%{
/* << Prologue starts here >> */

#include "ParserDefines.hpp"

#include "VariableDeclaration.hpp"
#include "IfElement.hpp"
#include "AssignmentElement.hpp"
#include "Function.hpp"
#include "FunctionArgumentList.hpp"
#include "LiteralExpression.hpp"
#include "VariableExpression.hpp"
#include "FunctionExpression.hpp"
#include "FunctionNotFoundException.hpp"
#include "VariableAlreadyDefinedException.hpp"
#include "VariableNotFoundException.hpp"
#include "TypeMismatchException.hpp"
#include "Variable.hpp"
#include "Block.hpp"
#include "NullElement.hpp"
#include "ExpressionElement.hpp"
#include "ForElement.hpp"
#include "LoopElement.hpp"
#include "Program.hpp"
#include "TrustedCast.hpp"
#include "ParsingErrorException.hpp"
#include "InvalidTypeException.hpp"

#include "lexer.hpp"

int yylex(yy::parser::semantic_type * sem, yy::parser::location_type * loc, Lexer * lexer)
{
	return lexer->yylex(sem, loc);
}

String makeOperatorFuncName(String const & op)
{
	static String const prefix("operator");
	return prefix + op;
}

ptr<FunctionExpression> makeUnary(SourceLocation const & loc, ptr<Object> s1, ptr<Object> s2)
{
	ptr<StrValue> opName = trusted_cast(s1);
	ptr<Expression> arg = trusted_cast(s2);
	ptr<FunctionArgumentList> args = new FunctionArgumentList(loc, arg);
	String funcName = makeOperatorFuncName(opName->value());
	return new FunctionExpression(loc, funcName, args);
}

ptr<FunctionExpression> makeBinary(SourceLocation const & loc, ptr<Object> s1, ptr<Object> s2, ptr<Object> s3)
{
	ptr<Expression> arg1 = trusted_cast(s1);
	ptr<StrValue> opName = trusted_cast(s2);
	ptr<Expression> arg2 = trusted_cast(s3);
	ptr<FunctionArgumentList> args = new FunctionArgumentList(loc, arg1, arg2);
	String funcName = makeOperatorFuncName(opName->value());
	return new FunctionExpression(loc, funcName, args);
}

/* << Prologue ends here >> */
%}

%require "2.4.1"

%parse-param {Lexer * lexer}
%parse-param {std::ostream * outputStream}
%lex-param {Lexer * lexer}
%error-verbose

%start program

%token NAME
%token LITERAL
%token KEYWORD_IF
%token KEYWORD_ELIF
%token KEYWORD_ELSE
%token KEYWORD_END
%token KEYWORD_FOR
%token KEYWORD_NEXT
%token KEYWORD_WHILE
%token KEYWORD_REPEAT
%right OP_ASSIGN			/* :=,+=,-=,*=,/=,%=,|=,&=,^=,$= */
%left OP_BINARY_0			/* <,>,<=,>=,==,!= */
%left OP_BINARY_1			/* |,^,$ */
%left OP_BINARY_2			/* *,/,%,&,>>,<< */
%left OP_UNARY				/* ~,! */
%left OP_XNARY				/* +,- */
%%

program		: block
				{
					ptr<Element> elem = trusted_cast($1);
					Program program(@$, elem, outputStream);
					program.execute();
					return 0;
				}
			;
block		: element
				{
					$$ = $1;
				}
			| block ';' element
				{
					ptr<Element> elem1 = trusted_cast($1);
					ptr<Element> elem2 = trusted_cast($3);
					$$ = elem1->concat(elem2);
				}
			;
element		: declaration
				{
					$$ = $1;
				}
			| command
				{
					$$ = $1;
				}
			;
declaration	: var_decl
				{
					$$ = $1;
				}
			;
var_decl	: type var_decl_list
				{
					ptr<StrValue> typeName = trusted_cast($1);
					ptr<VariableDeclarationList> vars = trusted_cast($2);
					Value::DataType type = Value::typeForName(typeName->value());
					if(type == Value::Null)
					{
						throw InvalidTypeException(@1, typeName->value());
					}
					$$ = new VariableDeclaration(@$, type, vars);
				}
type		: NAME
				{
					$$ = $1;
				}
			;
var_decl_list : var_declarator
				{
					ptr<Variable> var = trusted_cast($1);
					ptr<VariableDeclarationList> vars = new VariableDeclarationList();
					vars->addVariable(var);
					$$ = vars;				
				}
			| var_decl_list ',' var_declarator
				{
					ptr<VariableDeclarationList> vars = trusted_cast($1);
					ptr<Variable> var = trusted_cast($3);
					vars->addVariable(var);
					$$ = vars;
				}
			;
var_declarator : NAME '=' expression
				{
					ptr<StrValue> name = trusted_cast($1);
					ptr<Expression> initializer = trusted_cast($3);
					$$ = new Variable(@$, name->value(), initializer);
				}
			;
command		: null_cmd
				{
					$$ = $1;
				}
			| expr_cmd
				{
					$$ = $1;
				}
			| assign_cmd
				{
					$$ = $1;
				}
			| if_cmd
				{
					$$ = $1;
				}
			| for_cmd
				{
					$$ = $1;
				}
			| while_cmd
				{
					$$ = $1;
				}
			| repeat_cmd
				{
					$$ = $1;
				}
			;
null_cmd	: /* empty */
				{
					$$ = new NullElement(@$);
				}
			;
expr_cmd	: expression
				{
					ptr<Expression> expr = trusted_cast($1);
					$$ = new ExpressionElement(@$, expr);
				}
			;
assign_cmd	: NAME OP_ASSIGN expression
				{
					ptr<StrValue> varName= trusted_cast($1);
					ptr<StrValue> assignOp = trusted_cast($2);
					ptr<Expression> expr = trusted_cast($3);
					String funcName = makeOperatorFuncName(assignOp->value());
					$$ = new AssignmentElement(@$, varName->value(), funcName, expr);					
				}
			;
if_cmd		: if_branch elif_list else_branch
				{
					ptr<ElifBranch> thenBrach = trusted_cast($1);
					ptr<ElifBranchList> elifList = trusted_cast($2);
					ptr<Element> elseBranch = trusted_cast($3);
					$$ = new IfElement(@$, thenBrach, elifList, elseBranch);
				}
			;
if_branch	: KEYWORD_IF '(' expression ')' block
				{
					ptr<Expression> expr = trusted_cast($3);
					ptr<Element> body = trusted_cast($5);
					$$ = new ElifBranch(@$, expr, body);
				}
			;
elif_list	: /* empty */
				{
					$$ = new ElifBranchList();
				}
			| elif_list elif_branch
				{
					ptr<ElifBranchList> list = trusted_cast($1);
					ptr<ElifBranch> branch = trusted_cast($2);
				}
			;
elif_branch	: KEYWORD_ELIF '(' expression ')' block
				{
					ptr<Expression> expr = trusted_cast($3);
					ptr<Element> body = trusted_cast($5);
					$$ = new ElifBranch(@$, expr, body);
				}
			;
else_branch	: KEYWORD_END
				{
					$$ = new NullElement(@$);
				}
			| KEYWORD_ELSE block KEYWORD_END
				{
					$$ = $2;
				}
			;
for_cmd		: KEYWORD_FOR '(' element ';' for_cond ';' element ')' block KEYWORD_NEXT
				{
					ptr<Element> init = trusted_cast($3);
					ptr<Expression> expr = trusted_cast($5);
					ptr<Element> step = trusted_cast($7);
					ptr<Element> body = trusted_cast($9);
					$$ = new ForElement(@$, init, expr, step, body);
				}
			;
for_cond	: /* empty */
				{
					$$ = new LiteralExpression(@$, new BoolValue(true));
				}
			| expression
				{
					$$ = $1;
				}
			;
while_cmd	: KEYWORD_WHILE '(' expression ')' block KEYWORD_NEXT
				{
					ptr<Expression> expr = trusted_cast($3);
					ptr<Element> body = trusted_cast($5);
					$$ = new LoopElement(@$, true, expr, body);
				}
			;
repeat_cmd	: KEYWORD_REPEAT block KEYWORD_NEXT KEYWORD_WHILE '(' expression ')'
				{
					ptr<Element> body = trusted_cast($2);
					ptr<Expression> expr = trusted_cast($5);
					$$ = new LoopElement(@$, false, expr, body);
				}
			;
expression	: '(' expression ')'
				{
					$$ = $2;
				}
			| LITERAL
				{
					ptr<Value> value = trusted_cast($1);
					$$ = new LiteralExpression(@$, value);
				}
			| NAME
				{
					ptr<StrValue> varName = trusted_cast($1);
					$$ = new VariableExpression(@$, varName->value());
				}
			| NAME '(' invoke_list ')'
				{
					ptr<StrValue> funcName = trusted_cast($1);
					ptr<FunctionArgumentList> args = trusted_cast($3);
					$$ = new FunctionExpression(@$, funcName->value(), args);
				}
			| OP_UNARY expression
				{
					$$ = makeUnary(@$, $1, $2);
				}
			| OP_XNARY expression %prec OP_UNARY
				{
					$$ = makeUnary(@$, $1, $2);
				}
			| expression OP_BINARY_0 expression
				{
					$$ = makeBinary(@$, $1, $2, $3);
				}
			| expression OP_BINARY_1 expression
				{
					$$ = makeBinary(@$, $1, $2, $3);
				}
			| expression OP_BINARY_2 expression
				{
					$$ = makeBinary(@$, $1, $2, $3);
				}
			| expression OP_XNARY expression %prec OP_BINARY_1
				{
					$$ = makeBinary(@$, $1, $2, $3);
				}
			;
invoke_list	: /* empty */
				{
					$$ = new FunctionArgumentList(@$);
				}
			| expression
				{
					ptr<Expression> arg = trusted_cast($1);
					$$ = new FunctionArgumentList(@$, arg);
				}
			| invoke_list ',' expression
				{
					ptr<FunctionArgumentList> args = trusted_cast($1);
					ptr<Expression> arg = trusted_cast($3);
					args->addArgument(arg);
					$$ = args;
				}
			;
%%

void yy::parser::error(location_type const & loc, std::string const & msg)
{
	throw ParsingErrorException(loc, msg);
}
